package com.gmrz.uaf.remote.protocol.v1.processor;

import com.gmrz.service.challengestore.ChallengeStoreException;
import com.gmrz.service.challengestore.ChallengeStoreService;
import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.TLVType3;
import com.gmrz.uaf.common.UAFCommonRuntimeException;
import com.gmrz.uaf.common.UAFRuntimeException;
import com.gmrz.uaf.crypto.spi.UAFAuthAlgorithmSuite;
import com.gmrz.uaf.db.DAOException;
import com.gmrz.uaf.db.UAFDAOFactory;
import com.gmrz.uaf.db.UAFDBConnectionMgr;
import com.gmrz.uaf.db.dao.AuthenticatorDAO;
import com.gmrz.uaf.policy.verification.PolicyVerificationException;
import com.gmrz.uaf.protocol.v1.json.UAFSchemaBuilder;
import com.gmrz.uaf.protocol.v1.processor.UAFAuthInitProcessor;

import com.gmrz.uaf.protocol.v1.processor.exception.AuthenticatorNotRegisteredException;
import com.gmrz.uaf.protocol.v1.processor.exception.PolicyNotFoundException;
import com.gmrz.uaf.protocol.v1.processor.exception.TxnContentCreationException;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.protocol.v1.schema.*;
import com.gmrz.uaf.remote.plugin.AuthServicePluginManager;
import com.gmrz.uaf.server.ServerConfig;
import com.gmrz.uas.plugin.caservice.CertServicePlugin;
import com.gmrz.uas.plugin.caservice.bean.CertTransactionTextBean;
import com.gmrz.uas.plugin.exception.PluginException;
import com.gmrz.util.Convert;
import com.gmrz.util.CryUtil;
import com.gmrz.util.Strings;
import com.gmrz.util.db.DBUtil;
import com.google.gson.Gson;
import com.google.inject.Inject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 手机盾认证服务接口
 */
public class UAPCertAuthInitProcessor extends UAFAuthInitProcessor {

    private static final Logger LOG = LogManager.getLogger(UAPCertAuthInitProcessor.class);

    AuthServicePluginManager serviceManager;

    @Inject
    public void setServiceManager(AuthServicePluginManager serviceManager) {
        this.serviceManager = serviceManager;
    }

    @Override
    public List<AuthenticationRequest> request(String custNo, String hashedUserName, String appID, String deviceID, List<String> authType,
                                               String transType, String transactionText, byte[] challengeParam, String ext)
            throws PolicyNotFoundException, TxnContentCreationException, DAOException,
            AuthenticatorNotRegisteredException, PolicyVerificationException, PluginException {

        // 获取认证发起的响应数据
        Map<String, Object> resultMap = getAuthenticationRequest(custNo, hashedUserName, appID, deviceID, authType, transType, transactionText, challengeParam, ext);
        AuthenticationRequest request = (AuthenticationRequest) resultMap.get("request");
        String t_authType = (String) resultMap.get("t_authType");
        Set<Authenticator> registeredList = (Set<Authenticator>) resultMap.get("registeredList");
        Policy p = (Policy) resultMap.get("policy");
        if (t_authType.compareTo(Constants.AuthType.CERT_FINGERPRINT.getAuthType()) >= 0) {
            ExtBean resultExtBean = null;
            List<ExtBean> extBeans = new ArrayList<ExtBean>();
            List<List<MatchCriteria>> matchList = p.getAccepted();
            if (null != matchList) {
                for (List<MatchCriteria> mList : matchList) {
                    for (MatchCriteria m : mList) {
                        String keyId = Convert.toBase64(m.getKeyIDList().get(0).getId());
                        String aaid = m.getAAID().get(0).format();
                        for (Authenticator a : registeredList) {
                            String aKeyId = Convert.toBase64(a.getKeyID().getId());
                            String aAaId = a.getAAID().format();
                            if (keyId.equals(aKeyId) && aaid.equals(aAaId)) {
                                // 查询元数据中设置安全等级
                                String securityLevel = Strings.codeConversion(a.getMetadata().getSecureLevel().intValue());
                                long maxCount = getMaxCount(securityLevel);
                                ExtBean extBean = new ExtBean();
                                extBean.setAaid(aaid);
                                extBean.setKeyId(keyId);
                                byte[] key = new byte[16];
                                byte[] byte256 = CryUtil.getSHA256(request.getOperationHeader().getServerData().getChallenge());
                                System.arraycopy(byte256, 0, key, 0, 16);
                                byte[] certNoBytes = CryUtil.Aes256Encode(String.valueOf(maxCount).getBytes(), key);
                                extBean.setCertNo(Convert.toBase64(certNoBytes));
                                extBeans.add(extBean);
                                // 获取算法套件
                                int algoSuite = a.getMetadata().getAlgorithm();
                                UAFAuthAlgorithmSuite suite = null;
                                byte[] rawHash = null;
                                String showFlagBase64 = null;
                                byte[] showTextHash = null;
                                String showTestHashBase64 = null;
                                String textHashBash64 = null;
                                String esFlag = null;
                                String esHash = null;
                                try {
                                    // 根据算法套件去加密引擎中获取认证算法
                                    suite = this.cryptoEngine.getAuthAlgorithmSuite(algoSuite);
                                    rawHash = suite.hash(Convert.fromBase64(createCertTransactionText(ext, t_authType)));
                                    CertTransactionTextBean certTransactionTextBean = getCertTransactionText(ext, t_authType);
                                    ByteBuffer showFlagBuf = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN);
                                    showFlagBuf.putShort(Short.valueOf(certTransactionTextBean.getShowFlag()));
                                    showFlagBase64 = Convert.toBase64(showFlagBuf.array());
                                    showTextHash = suite.hash(certTransactionTextBean.getShowText().getBytes(Constants.UTF_8));
                                    showTestHashBase64 = Convert.toBase64(showTextHash);
                                    textHashBash64 = Convert.toBase64(suite.hash(Convert.fromBase64(ext)));
                                    esFlag = certTransactionTextBean.getEsFlag();
                                    esHash = certTransactionTextBean.getEsHash();
                                } catch (TxnContentCreationException te) {
                                    throw te;
                                } catch (Exception e) {
                                    LOG.error("UAPCertAuthInitProcessor request error", e);
                                    throw new TxnContentCreationException(UAFErrorCode.PROTOCOL_TXN_CONTENT_CREATE_FAILED);
                                }
                                request.getOperationHeader().getServerData().putValidateDataHash(Constants.CERT_TRANSACTION_TEXT + a.getAAID().format(), Convert.toBase64(rawHash));
                                request.getOperationHeader().getServerData().putValidateDataHash(Constants.CERT_TRANSACTION_SHOWFLAG + a.getAAID().format(), showFlagBase64);
                                request.getOperationHeader().getServerData().putValidateDataHash(Constants.CERT_TRANSACTION_SHOWTEXT + a.getAAID().format(), showTestHashBase64);
                                request.getOperationHeader().getServerData().putValidateDataHash(Constants.CERT_TRANSACTION_EXT_TEXT + a.getAAID().format(), textHashBash64);
                                request.getOperationHeader().getServerData().putValidateDataHash(Constants.CERT_TRANSACTION_ES_FLAG + a.getAAID().format(), esFlag);
                                request.getOperationHeader().getServerData().putValidateDataHash(Constants.CERT_TRANSACTION_ES_HASH + a.getAAID().format(), esHash);

                            }
                        }
                    }
                }
                if (null == resultExtBean) {
                    resultExtBean = new ExtBean();
                }
                resultExtBean.setCertNos(extBeans);
            }
            if (Strings.isNotBlank(ext)) {
                if (null == resultExtBean) {
                    resultExtBean = new ExtBean();
                }
                // 将创建的 P7 格式的 证书交易内容放入到扩展类
                resultExtBean.setP7data(createCertTransactionText(ext, t_authType));
                // 将扩展等数据经过base64编码后放入redis中
                setExtHashToRedis(Convert.toBase64(request.getOperationHeader().getServerData().getChallenge()), hashedUserName, ext);
            }
            if (null != resultExtBean) {
                List<Extension> extensions = request.getOperationHeader().getExtensions();
                if (null == extensions) {
                    extensions = new ArrayList<Extension>();
                }
                Gson gson = UAFSchemaBuilder.getGson();
                Extension extension = new Extension();
                extension.setId("cert");
                extension.setData(Convert.toBase64(gson.toJson(resultExtBean).getBytes()));
                extensions.add(extension);
                // 将P7 格式的交易内容等信息放入到 exts扩展中
                request.getOperationHeader().setExtensions(extensions);
            }
        }
        List requests = new ArrayList();
        requests.add(request);
        return requests;
    }


    private CertificateBean getCertNo(String authId)
            throws DAOException {
        Connection conn = null;
        try {
            conn = UAFDBConnectionMgr.getConnection();
            AuthenticatorDAO adao = UAFDAOFactory.createAuthenticatorDAO(conn);
            CertificateBean certificateBean = adao.findCertByAuthId(authId);
            return certificateBean;
        } catch (SQLException e) {
            LOG.error("Failed to get cert", e);
            throw new DAOException(UAFErrorCode.DB_DATABASE_CONNECTION_ERROR);
        } finally {
            DBUtil.close(conn);
        }
    }

    private String createCertTransactionText(String ext, String t_authType)
            throws TxnContentCreationException {
        // 从扩展中将base64编码的交易数据进行解码，并生成 utf8 编码的字符串
        String decodedTextString = getTextFormBase64(ext);
        try {
            // 根据认证方式获取 证书服务插件
            CertServicePlugin certServicePlugin = serviceManager.getCertServicePlugin(t_authType);
            // 获取证书交易内容对象
            CertTransactionTextBean certTransactionTextBean = certServicePlugin.getCertTransactionText(decodedTextString);
            int transactionLength = 2048;
            String transactionLengthStr = this.getServerConfig().getUasConfig("uaf.cert.transaction.length");
            if (Strings.isNotBlank(transactionLengthStr)) {
                transactionLength = Integer.parseInt(transactionLengthStr);
            }
            if (decodedTextString.getBytes(Constants.UTF_8).length > transactionLength) {
                throw new TxnContentCreationException(UAFErrorCode.PROTOCOL_TXN_CONTENT_LENGTH_FAILED);
            }
            // 长度校验
            if (null != certTransactionTextBean.getShowText() && certTransactionTextBean.getShowText().getBytes(Constants.UTF_8).length > Constants.TRANSACTION_SHOW_LENGTH) {
                throw new TxnContentCreationException(UAFErrorCode.PROTOCOL_TXN_CONTENT_LENGTH_FAILED);
            }
            TLVType3 certAuthTradeBaseTag = new TLVType3() {
            };
            certAuthTradeBaseTag.addTLVSegment(Constants.TAG_CERT_AUTH_TRADE_BASE, decodedTextString.getBytes(Constants.UTF_8));
            // byte[] certAuthTradeBaseBytes = certAuthTradeBaseTag.getTLVBytes();

//            TLVType3 certAuthTradeIshowTag = new TLVType3() {
//            };
            ByteBuffer showFlagBuf = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN);
            showFlagBuf.putShort(Short.valueOf(certTransactionTextBean.getShowFlag()));
            certAuthTradeBaseTag.addTLVSegment(Constants.TAG_CERT_AUTH_TRADE_ISHOW, showFlagBuf.array());
//            byte[] certAuthTradeIshowBytes = certAuthTradeIshowTag.getTLVBytes();

//            TLVType3 certAuthTradeShowdataTag = new TLVType3() {
//            };
            certAuthTradeBaseTag.addTLVSegment(Constants.TAG_CERT_AUTH_TRADE_SHOWDATA, null == certTransactionTextBean.getShowText() ? null : certTransactionTextBean.getShowText().getBytes(Constants.UTF_8));
//            byte[] certAuthTradeShowdataBytes = certAuthTradeShowdataTag.getTLVBytes();

            if (Strings.isNotBlank(certTransactionTextBean.getEsFlag())) {
                if ("01".equals(certTransactionTextBean.getEsFlag()) && Strings.isNullOrEmpty(certTransactionTextBean.getEsHash())) {
                    throw new TxnContentCreationException(UAFErrorCode.PROTOCOL_VALIDATE_P7_ES_HASH_EMPTY);
                }
                ByteBuffer esFlagBuf = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN);
                esFlagBuf.putShort(Short.valueOf(certTransactionTextBean.getEsFlag()));
                certAuthTradeBaseTag.addTLVSegment(Constants.TAG_CERT_AUTH_TRADE_ISES, esFlagBuf.array());
            }
            if (Strings.isNotBlank(certTransactionTextBean.getEsHash())) {
                certAuthTradeBaseTag.addTLVSegment(Constants.TAG_CERT_AUTH_TRADE_ESHASH, Convert.fromBase64(certTransactionTextBean.getEsHash()));
            }
            byte[] certAuthTradeBaseTagBytes = certAuthTradeBaseTag.getTLVBytes();


            ByteBuffer buffer = ByteBuffer.allocate(certAuthTradeBaseTagBytes.length).order(ByteOrder.LITTLE_ENDIAN);
            buffer.put(certAuthTradeBaseTagBytes);

            TLVType3 certAuthTransactionTag = new TLVType3() {
            };
            certAuthTransactionTag.addTLVSegment(Constants.TAG_CERT_AUTH_TRANSACTION, buffer.array());
            byte[] certAuthTransactionBytes = certAuthTransactionTag.getTLVBytes();
            return Convert.toBase64(certAuthTransactionBytes);
        } catch (TxnContentCreationException te) {
            throw te;
        } catch (Exception e) {
            LOG.error("createCertTransactionText error text:{}", ext, e);
            throw new TxnContentCreationException(UAFErrorCode.PROTOCOL_TXN_CONTENT_CREATE_FAILED);
        }
    }

    private String getTextFormBase64(String textBase64)
            throws TxnContentCreationException {
        try {
            byte[] decodedTextBytes = Convert.fromBase64(textBase64);
            String decodedTextString = Convert.fromUtf8(decodedTextBytes);
            LOG.info("Transaction text decoded from Base64[{}]", decodedTextString);
            return decodedTextString;
        } catch (Exception e) {
            LOG.error("createCertTransactionText error text:{}", textBase64, e);
            throw new TxnContentCreationException(UAFErrorCode.PROTOCOL_TXN_CONTENT_CREATE_FAILED);
        }
    }

    private CertTransactionTextBean getCertTransactionText(String ext, String t_authType)
            throws TxnContentCreationException {
        String decodedTextString = getTextFormBase64(ext);
        try {
            CertServicePlugin certServicePlugin = serviceManager.getCertServicePlugin(t_authType);
            CertTransactionTextBean certTransactionTextBean = certServicePlugin.getCertTransactionText(decodedTextString);
            return certTransactionTextBean;
        } catch (Exception e) {
            LOG.error("createCertTransactionText error text:{}", ext, e);
            throw new TxnContentCreationException(UAFErrorCode.PROTOCOL_TXN_CONTENT_CREATE_FAILED);
        }
    }

    private void setExtHashToRedis(String challenge, String hashedUserName, String ext) {
        String extHash = Convert.toBase64(CryUtil.getSHA256(Convert.fromBase64(ext)));
        ChallengeStoreService uafChallengeStoreService = this.getmUafChallengeStoreService();
        try {
            uafChallengeStoreService.setRedisValue(Constants.UAF_CERT_AUTH_EXT_HASH + challenge + "-" + hashedUserName, this.getServerConfig().getChallengeValiditySeconds(), extHash);
        } catch (ChallengeStoreException e) {
            throw new UAFCommonRuntimeException(e.getMessage());
        }
    }
}

